package top.onedream.buildsmallsoftware.component.messages

import io.netty.handler.codec.http.HttpHeaders
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import org.springframework.util.MultiValueMap
import org.yeauty.annotation.*
import org.yeauty.pojo.Session
import top.onedream.buildsmallsoftware.model.message.SocketUtil
import top.onedream.buildsmallsoftware.model.user.LoginUser
import top.onedream.buildsmallsoftware.model.user.User
import top.onedream.buildsmallsoftware.util.JwtUtil
import java.util.concurrent.ConcurrentHashMap


/**
 * nio网络套接字
 * @author zhangkein
 * @date 2023/05/19
 * @constructor 创建[NioWebSocket]
 */
@Component
@ServerEndpoint(path = "/ws/{token}", port = "9527")
class NioWebSocket {
    private val log: Logger = LoggerFactory.getLogger(NioWebSocket::class.java)

    companion object {
        val socketUtil: SocketUtil = SocketUtil()

        /**
         * 用户channel映射关系
         */

        val userChannelMap = ConcurrentHashMap<Long, Session>()

        /**
         * 单点推送给某个人
         * 发送消息给用户
         * @param [session] 会话
         * @param [msg] 味精
         */
        fun sendMessageToUser(session: Session, msg: String?) {
            if (session.isOpen && session.isActive) {
                val tws = TextWebSocketFrame(msg)
                session.sendText(tws)
            }
        }

        fun sendMessageToUserByUserId(userId: Long?, message: String?) {
            if (userId != null) {
                userChannelMap[userId]?.let { session ->
                    sendMessageToUser(session, message)
                }
            }
        }
    }

    private val successFlag = "PING"
    private val replayFlag = "PONG"


    /**
     * 当有新的WebSocket连接完成时，对该方法进行回调
     * 在开放
     * @param [session] 会话
     * @param [headers] 头
     * @param [req] 要求事情
     * @param [reqMap] 要求地图
     * @param [token] 用户id
     * @param [pathMap] 路径图
     */
    @OnOpen
    fun onOpen(
        session: Session?, headers: HttpHeaders?,
        @RequestParam req: String?,
        @RequestParam reqMap: MultiValueMap<*, *>?,
        @PathVariable token: String,
        @PathVariable pathMap: Map<*, *>?
    ) {
        val userId = getUserId(token)
        log.info("一个用户建立链接")
        log.info("用户 $userId 上线")
        // 模拟获取到的token
        if (userId != null && userChannelMap[userId] == null) {
            userChannelMap[userId] = session!!
        }
        log.info("当前在线人数：${userChannelMap.size}")
    }


    /**
     * 当接收到字符串消息时，对该方法进行回调
     *
     * @param session
     * @param message
     */
    @OnMessage
    fun onMessage(session: Session?, message: String) {
//        log.info(">o< 接收到消息: $message")
        if (successFlag.lowercase() == message.lowercase()) {
            sendMessageToUser(session!!, replayFlag)
        }
    }

    /**
     * 当有WebSocket连接关闭时，对该方法进行回调
     * 在关闭
     * @param [session] 会话
     * @param [token] 用户id
     */
    @OnClose
    fun onClose(session: Session, @PathVariable token: String) {
        val userId = getUserId(token)
        log.info("一个用户断开链接")
        if (userId != null && userChannelMap[userId] != null) {
            userChannelMap.remove(userId)
        }
        session.close()
        log.info("用户 $userId 离线")
        log.info("当前在线人数：${userChannelMap.size}")
    }


    /**
     * 当有WebSocket抛出异常时，对该方法进行回调
     *
     * @param session
     * @param throwable
     */
    @OnError
    fun onError(session: Session?, @PathVariable arg: String?, throwable: Throwable) {
        log.info("一个链接发生错误")
        log.error(throwable.message, throwable)
        onClose(session!!, arg!!)
    }

    /**
     * 当有新地连接进入时，对该方法进行回调
     * 可做简单的鉴权
     * 握手
     * @param [session] 会话
     * @param [headers] 头
     * @param [req] 要求事情
     * @param [reqMap] 要求地图
     * @param [token] 用户id
     * @param [pathMap] 路径图
     */
    @BeforeHandshake
    fun handshake(
        session: Session, headers: HttpHeaders?,
        @RequestParam req: String?,
        @RequestParam reqMap: MultiValueMap<*, *>?,
        @PathVariable token: String?,
        @PathVariable pathMap: Map<*, *>?
    ) {
        session.setSubprotocols("stomp")
        try {
            if (token != null && token.length > 20) {
                val map: Map<String, Any> = JwtUtil.parseJWT(token, socketUtil.secret!!) // 掉用jwt解密方法解密
                val value = map["user"] as String
                val user = socketUtil.objectMapper?.readValue(value, User::class.java)
                val target: LoginUser =
                    socketUtil.redisCache?.getCacheObject("loginUser:" + user!!.id) ?: throw Exception()
            } else {
                throw Exception()
            }
        } catch (e: Exception) {
            sendMessageToUser(session, "path error")
            session.close()
        }
    }

    fun getUserId(token: String): Long? {
        if (token.length > 20) {
            val map: Map<String, Any> = JwtUtil.parseJWT(token, socketUtil.secret!!) // 掉用jwt解密方法解密
            val value = map["user"] as String
            val user = socketUtil.objectMapper?.readValue(value, User::class.java)
            if (user != null) {
                return user.id
            }
        }
        return null
    }
}